#include "../include/kc.h"
#include "../include/buffer.h"
#include "../include/screensaver.h"
#include "../include/keyboard.h"
#include "../include/scheduler.h"
#include "../include/shell.h"
#include "../include/programs.h"
#include "../include/syscalls.h"

int killComm(int argc, char ** argv);
int mkdirComm(int argc, char ** argv);
int cdComm(int argc, char ** argv);
int clearComm(int argc, char ** argv);
int renameComm(int argc, char ** argv);
int rmComm(int argc, char ** argv);
int rmdirComm(int argc, char ** argv);


//estructura para guardar informacion de los comandos
typedef struct info{
	char * label;
	int cant_args;
	int (*function)(int argc, char ** argv);
	char isComm;

}info_t;

info_t com_table[COM_N]= {
		{"fortune", 0, fortune, 0},
		{"help", 0, help, 0},
		{"charLifter", 1, char_lifter, 0},
		{"frase", 2, frase, 0},
		{"sampleText", 0, sample_text, 0},
		{"sampleCommands", 0, sample_commands, 0},
		{"top", 0, top, 0},
		{"illegalAccesor", -1, illegalAccessor, 0},
		{"cpuConsumer", 0, cpuConsumer, 0},
		{"cpuYielder", 0, cpuYielder, 0},
		{"timer", -1, timer, 0},
		{"kill", 1, killComm, 1},
		{"mkdir", 1, mkdirComm, 1},
		{"cd", -1, cdComm, 1},
		{"rename", -1, renameComm, 1},
		{"clear", 0, clearComm, 1},
		{"rm", -1, rmComm, 1},
		{"rmdir", -1, rmdirComm, 1},
		{"ls", -1, ls, 0},
		{"find", -1, findFile, 0},
		{"cat", -1, cat, 0},
		{"write", -1, writer, 0},
		{"fill", -1, fill, 0}
};

int prompt(command_t command, char * command_string, buffer_t * prom_buff, int *com_index, int *round_cicle, char past_command_matrix[MAX_COM_INDEX][COM_LEN]);
int exec_command(command_t command);
int com_exists(char * command_string, command_t command);
int build_command(char * command_string, int code, command_t command);
int get_name(char * source, char * dest);
void save_command(char * command_string, int *com_index, int *round_cicle, char past_command_matrix[MAX_COM_INDEX][COM_LEN]);

/*
 * Maneja los comando ingresados por el prompt.
 */

int
shell(int argc, char* argv[]){

	command_t command;

	char command_string[COM_LEN];

	if (argc == 1)
		printstring(argv[0]);

	//buffer del prompt
	buffer_t prom_buff;

	//indice, flag y matriz para los comandos pasados
	int com_index =0;
	int round_cicle=0;
	char past_command_matrix[MAX_COM_INDEX][COM_LEN];

	//inicializo el buffer del prompt, y la estructura del comando.
	inibuff(&prom_buff);
	struct commandCDT str1;
	command = &(str1);

	//path del directorio actual de la shell
	char currDirPath[75];
	getcwd(currDirPath, 75);

	while(1)
	{
		printstring("Command:");
		printstring(currDirPath);
		printstring("$ ");
		switch(prompt(command, command_string, &prom_buff, &com_index, &round_cicle, past_command_matrix)){
			case TOO_MANY_ARGS: printstring("Too Many Arguments \n");break;
			case TOO_FEW_ARGS: printstring("Too Few Arguments \n");break;
			case UNKNOWN: printstring("Unknown Command \n");break;
			case ARG_TOO_LONG: printstring("Argument Too Long \n");break;
			case ERR_IN_ARG: printstring("Error In Argument \n");break;
			case COM_EMPTY: break;
			default: {	if( ! exec_command(command))
							printstring("Error Executing Command \n");
						else {
							//si hice un cd, actualizo el currDirPath
							if (command->isComm == 1 && strcmp(command->name, "cd") == 0)
								getcwd(currDirPath, 75);
						}
					 }
		}
	}

	return 1;
}

/*
 * Ejecuta el comando llamando al puntero a funcion de la estructura
 * (la funcion wrapper).
 */

int
exec_command(command_t command){

	int i, childPid;
	int cant = command->cant_arguments;
	char * args[MAX_ARGS];

	for(i=0; i < cant; i++){
		args[i] = command->arguments[i];
	}

	if(command->isComm){
		return command->function(command->cant_arguments, args);
	}

	childPid = startProcess(command->function, command->name, command->backgroundFlag,
			command->cant_arguments, (char **)args, command->priority);

	if(childPid !=0 && ! command->backgroundFlag) {
		//espero a que mi hijo se muera
		waitPid(childPid);
		//arreglo cosas de la video que mi hijo podria dejar mal
		showCursor();
	}

	return childPid;

}

/*
 * Arma el comando a ejecutar, para enviar a shell.
 */

int
prompt(command_t command, char * command_string, buffer_t * prom_buff, int *com_index,
		int *round_cicle, char past_command_matrix[MAX_COM_INDEX][COM_LEN]){

	int c, cant, code;
	int i, j=1, index, cicles=MAX_COM_INDEX, char_cant=0;
	char a;

	//mientras no llegue un enter, armo el comando.
	while((c=getchar())!='\n')
	{
		switch(c){
		case 0:break;
//Caracteres de scroll, no deben ser almacenados en el buffer del prompt
		case ASCII_PG_DOWN: case ASCII_PG_UP: putchar(c); break;
		//si se ingresa flecha para arriba o abajo voy a buscar el comando
		//almacenado
		case ASCII_KEY_UP: case ASCII_KEY_DOWN:
				//si no hay ninguno ingresado no hago nada
				if((*com_index)!=0){
					index=(*com_index) - j + ((c==ASCII_KEY_DOWN)? 2 : 0);
				//indica si me pase del limite de comando a almacenar, y
					//modifica el index de que ir a buscar
					if((*round_cicle)){
						if(index<0)
							index=index+MAX_COM_INDEX;
					}
					if(index >= 0 && ((c==ASCII_KEY_UP && cicles>0)
							|| (c==ASCII_KEY_DOWN && cicles>=0 && cicles <10))){
						//limpiar linea en pantalla
						for(i=0;i<char_cant;i++)
							putchar('\r');
						getbuff(prom_buff, &a, 1);		//limpio el buffer

	//cuando llego al final para abajo, borro. Sino levanto el comando indicado
						if(c==ASCII_KEY_DOWN && cicles ==9){
							printstring("");
							char_cant=0;
						}else{
							putbuff_until(prom_buff,past_command_matrix[index],
									COM_LEN-1, '\0');
							printstring(past_command_matrix[index]);
							char_cant=strlen(past_command_matrix[index]);
						}

						if(c==ASCII_KEY_UP)
						{	j++;
							cicles--;
						}else
						{	j--;
							cicles++;
						}
					}
				}break;
		//armo el comando a partir de la entrada.
		default: switch(c){
				case '\r': if(char_cant){removecharbuff(prom_buff); putchar(c); char_cant--;}break;
				default : putcharbuff(prom_buff,c); putchar(c); char_cant++;
			}
		}
	}

	//termino de armar el comando y lo paso al arreglo command_string
	if(c=='\n'){
		putchar(c);
		putcharbuff(prom_buff,'\0');
		cant = getbuff(prom_buff, command_string, COM_LEN);
		char_cant=0;
	}

	//si no es un comando vacio, lo guardo
	if(command_string[0]!='\0')
		save_command(command_string, com_index, round_cicle, past_command_matrix);

	//si existe lo armo, sino retorno el codigo de error
	if((code = com_exists(command_string, command)) < COM_N)
		return build_command(command_string, code, command);
	else
		return code;

}

/*
 * Guarda el comando en la matriz de comandos pasados
 */

void
save_command(char * command_string, int *com_index, int *round_cicle, char past_command_matrix[MAX_COM_INDEX][COM_LEN]){

	if(*com_index == MAX_COM_INDEX){
		*com_index=0;
		*round_cicle=1;	//prendo el flag para avisar que me pase del maximo
	}

	strcpy(past_command_matrix[*com_index], command_string);
	(*com_index)++;

}

/*
 * Chequea si existe el comando en la tabla de comandos
 */

int
com_exists(char * command_string, command_t command){
	int i, code;

	if((code = get_name(command_string, command->name)) == 1)
	{
		for(i=0; i < COM_N; i++){
			if(strcmp(command->name, com_table[i].label) == 0)
				return i;
		}
		return UNKNOWN;
	}
	return code;
}

/*
 * Levanta el nombre del comando y lo pone en la estructura
 */

int
get_name(char * source, char * dest){

	int c, i, j;

	i=0;j=0;

	while(source[i] == ' '){
		i++;
	}

	while((c = source[i]) != ' ' && c != '\0'){
		if(j == NAME_LEN)
			return UNKNOWN;
		dest[j] = c;
	//piso el nombre con espacios, para no procesarlo denuevo en build_command
		source[i]= ' ';
		i++;
		j++;
	}

	if(j != 0){
		dest[j] = '\0';
		return 1;
	}
	return COM_EMPTY;
}

/*
 * Arma la estructura del comando a partir del string recibido
 */

int
build_command(char * command_string, int code, command_t command){

	int i = 0, j = 0, c;
	int cant_param = 0, cant_args;

	cant_args = com_table[code].cant_args;

	//si es -1 significa argumentos variables, hasta el maximo de argumentos
	if(cant_args == -1){
		command->cant_arguments = MAX_ARGS;
	}else
		command->cant_arguments = cant_args;

	command->function = com_table[code].function;
	command->isComm = com_table[code].isComm;

	command->priority = 1;
	command->backgroundFlag = 0;

	while(command_string[i] != '\0'){
		while(command_string[i] == ' '){
			i++;
		}

		if(command_string[i] == '"'){
			i++;
			while((c = command_string[i]) != '"'){
				if(j == MAX_ARG_LEN)
					return ARG_TOO_LONG;

				if(c == '\0')
					return ERR_IN_ARG;

				command->arguments[cant_param][j] = (char)c;
				i++;
				j++;
			}
			i++;
		}else{

			while((c = command_string[i]) != ' ' && c != '\0'){
				if(j == MAX_ARG_LEN)
					return ARG_TOO_LONG;

				command->arguments[cant_param][j] = (char)c;
				i++;
				j++;
			}
		}

		if(j != 0){
			command->arguments[cant_param][j] = '\0';
			cant_param++;
		}

		//me fijo si me pidieron correr el proceso con cierta prioridad
		if(command->arguments[cant_param-1][0] == '-' &&
				command->arguments[cant_param-1][1] == 'p') {
			command->priority = atoi(&command->arguments[cant_param-1][2]);
			cant_param--;
		}

		//me fijo si hay un & para correr el comando en el background
		if(command->arguments[cant_param-1][0] == '&') {
			command->backgroundFlag = 1;
			cant_param--;
		}

		if(cant_param > command->cant_arguments){
			return TOO_MANY_ARGS;
		}

		j=0;
	}

	if(cant_args != -1 && cant_param < command->cant_arguments)
				return TOO_FEW_ARGS;

	//si era argumentos variables, asigno la verdadera cantidad que me llego
	if (cant_args == -1) {
		command->cant_arguments = cant_param;
	}

	return 1;
}

int
killComm(int argc, char ** argv) {

	kill(atoi(argv[0]));
	return 1;
}

int
mkdirComm(int argc, char ** argv) {
	if (mkdir(argv[0], 0) == -1) {
		printstring("No se pudo crear el directorio\n");
	}
	return 1;
}

int
cdComm(int argc, char ** argv) {
	char * path;
	if (argc == 0)
		path = "/";
	else
		path = argv[0];

	if (chdir(path) == -1) {
		printstring("No se pudo entrar al directorio\n");
	}
	return 1;
}

int
renameComm(int argc, char ** argv) {
	if (argc != 2) {
		printstring("Modo de uso: rename nombreViejo nombreNuevo\n");
		return 1;
	}

	if (rename(argv[0], argv[1]) < 0) {
		printstring("No se pudo renombrar el archivo\n");
	}
	return 1;
}

int
clearComm(int argc, char ** argv) {
	clear();
	return 1;
}

int
rmComm(int argc, char ** argv) {
	if (argc != 1) {
		printstring("Modo de uso: rm nombreArchivo\n");
		return 1;
	}

	if (unlink(argv[0]) < 0) {
		printstring("No se pudo borrar el archivo\n");
	}

	return 1;
}
int
rmdirComm(int argc, char ** argv) {
	if (argc != 1) {
		printstring("Modo de uso: rmdir nombreDirectorio\n");
		return 1;
	}

	if (rmdir(argv[0]) < 0) {
		printstring("No se pudo borrar el directorio\n");
	}

	return 1;
}

